CodeMirror.defineMode("r", function (config) {
    function wordObj(str) {
        var words = str.split(" "), res = {};
        for (var i = 0; i < words.length; ++i) res[words[i]] = true;
        return res;
    }

    var atoms = wordObj("NULL NA Inf NaN NA_integer_ NA_real_ NA_complex_ NA_character_");
    var builtins = wordObj("list quote bquote eval return call parse deparse");
    var keywords = wordObj("if else repeat while function for in next break");
    var blockkeywords = wordObj("if else repeat while function for");
    var opChars = /[+\-*\/^<>=!&|~$:]/;
    var curPunc;

    function tokenBase(stream, state) {
        curPunc = null;
        var ch = stream.next();
        if (ch == "#") {
            stream.skipToEnd();
            return "comment";
        } else if (ch == "0" && stream.eat("x")) {
            stream.eatWhile(/[\da-f]/i);
            return "number";
        } else if (ch == "." && stream.eat(/\d/)) {
            stream.match(/\d*(?:e[+\-]?\d+)?/);
            return "number";
        } else if (/\d/.test(ch)) {
            stream.match(/\d*(?:\.\d+)?(?:e[+\-]\d+)?L?/);
            return "number";
        } else if (ch == "'" || ch == '"') {
            state.tokenize = tokenString(ch);
            return "string";
        } else if (ch == "." && stream.match(/.[.\d]+/)) {
            return "keyword";
        } else if (/[\w\.]/.test(ch) && ch != "_") {
            stream.eatWhile(/[\w\.]/);
            var word = stream.current();
            if (atoms.propertyIsEnumerable(word)) return "atom";
            if (keywords.propertyIsEnumerable(word)) {
                if (blockkeywords.propertyIsEnumerable(word)) curPunc = "block";
                return "keyword";
            }
            if (builtins.propertyIsEnumerable(word)) return "builtin";
            return "variable";
        } else if (ch == "%") {
            if (stream.skipTo("%")) stream.next();
            return "variable-2";
        } else if (ch == "<" && stream.eat("-")) {
            return "arrow";
        } else if (ch == "=" && state.ctx.argList) {
            return "arg-is";
        } else if (opChars.test(ch)) {
            if (ch == "$") return "dollar";
            stream.eatWhile(opChars);
            return "operator";
        } else if (/[\(\){}\[\];]/.test(ch)) {
            curPunc = ch;
            if (ch == ";") return "semi";
            return null;
        } else {
            return null;
        }
    }

    function tokenString(quote) {
        return function (stream, state) {
            if (stream.eat("\\")) {
                var ch = stream.next();
                if (ch == "x") stream.match(/^[a-f0-9]{2}/i);
                else if ((ch == "u" || ch == "U") && stream.eat("{") && stream.skipTo("}")) stream.next();
                else if (ch == "u") stream.match(/^[a-f0-9]{4}/i);
                else if (ch == "U") stream.match(/^[a-f0-9]{8}/i);
                else if (/[0-7]/.test(ch)) stream.match(/^[0-7]{1,2}/);
                return "string-2";
            } else {
                var next;
                while ((next = stream.next()) != null) {
                    if (next == quote) {
                        state.tokenize = tokenBase;
                        break;
                    }
                    if (next == "\\") {
                        stream.backUp(1);
                        break;
                    }
                }
                return "string";
            }
        };
    }

    function push(state, type, stream) {
        state.ctx = {
            type: type,
            indent: state.indent,
            align: null,
            column: stream.column(),
            prev: state.ctx
        };
    }

    function pop(state) {
        state.indent = state.ctx.indent;
        state.ctx = state.ctx.prev;
    }

    return {
        startState: function (base) {
            return {
                tokenize: tokenBase,
                ctx: {
                    type: "top",
                    indent: -config.indentUnit,
                    align: false
                },
                indent: 0,
                afterIdent: false
            };
        },

        token: function (stream, state) {
            if (stream.sol()) {
                if (state.ctx.align == null) state.ctx.align = false;
                state.indent = stream.indentation();
            }
            if (stream.eatSpace()) return null;
            var style = state.tokenize(stream, state);
            if (style != "comment" && state.ctx.align == null) state.ctx.align = true;

            var ctype = state.ctx.type;
            if ((curPunc == ";" || curPunc == "{" || curPunc == "}") && ctype == "block") pop(state);
            if (curPunc == "{") push(state, "}", stream);
            else if (curPunc == "(") {
                push(state, ")", stream);
                if (state.afterIdent) state.ctx.argList = true;
            }
            else if (curPunc == "[") push(state, "]", stream);
            else if (curPunc == "block") push(state, "block", stream);
            else if (curPunc == ctype) pop(state);
            state.afterIdent = style == "variable" || style == "keyword";
            return style;
        },

        indent: function (state, textAfter) {
            if (state.tokenize != tokenBase) return 0;
            var firstChar = textAfter && textAfter.charAt(0), ctx = state.ctx,
                closing = firstChar == ctx.type;
            if (ctx.type == "block") return ctx.indent + (firstChar == "{" ? 0 : config.indentUnit);
            else if (ctx.align) return ctx.column + (closing ? 0 : 1);
            else return ctx.indent + (closing ? 0 : config.indentUnit);
        }
    };
});

CodeMirror.defineMIME("text/x-rsrc", "r");
